package services

import (
	"context"
	"gitee.com/igolang/imoney/internal/app/system/entities"
	"gitee.com/igolang/imoney/internal/app/system/entities/constants"
	"gitee.com/igolang/imoney/internal/app/system/tools"
	"gitee.com/igolang/imoney/internal/common/httpResp"
	commonTools "gitee.com/igolang/imoney/internal/common/tools"
	"gitee.com/igolang/imoney/internal/models/dao"
	"gitee.com/igolang/imoney/internal/models/model"
	"gitee.com/igolang/pkg/xgorm"
	"gitee.com/igolang/pkg/xptr"
	"gitee.com/igolang/pkg/xtime"
	"gitee.com/igolang/pkg/xutils"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"gorm.io/gen"
	"net/http"
	"slices"
	"strings"
	"time"
)

var Menu = &menu{}

type menu struct{}

func (s *menu) Routers(ctx *gin.Context) ([]*entities.MenuRouterItem, error) {
	conds := []gen.Condition{
		dao.SysMenu.Status.Eq(constants.OkStatus),
		dao.SysMenu.MenuType.Neq(entities.MenuTypeF),
	}
	if cond := s.formatUserConds(ctx); cond != nil {
		conds = append(conds, cond)
	}
	rows, err := dao.SysMenu.Where(conds...).Order(dao.SysMenu.ParentID, dao.SysMenu.OrderNum).Find()
	if err != nil {
		return nil, err
	}
	list := make([]*entities.MenuRouterItem, 0)
	if len(rows) == 0 {
		return list, nil
	}
	dataMap := make(map[int64]*entities.MenuRouterItem)
	dirIds := make([]int64, 0)
	for _, row := range rows {
		item := s.formatMenu(row)
		if row.ParentID == 0 && row.MenuType == entities.MenuTypeC {
			item.Component = "Layout"
			item.Path = "/"
			item.Name = "Parent" + item.Name
			item.AlwaysShow = false
			citem := s.formatMenu(row)
			citem.Path = row.Path
			item.Children = append(item.Children, citem)
		}
		if row.MenuType == entities.MenuTypeM {
			dirIds = append(dirIds, row.MenuID)
		}
		dataMap[row.MenuID] = item
		if row.ParentID == 0 {
			list = append(list, item)
		}
	}
	for _, v := range rows {
		if v.ParentID == 0 {
			continue
		}

		if !slices.Contains(dirIds, v.ParentID) {
			continue
		}

		dataMap[v.ParentID].Children = append(dataMap[v.ParentID].Children, dataMap[v.MenuID])
	}

	for _, v := range dataMap {
		if len(v.Children) == 0 {
			v.Children = nil
			v.Redirect = ""
			v.AlwaysShow = false
		}
	}
	return list, nil
}

func (s *menu) formatMenu(row *model.SysMenu) *entities.MenuRouterItem {
	item := &entities.MenuRouterItem{
		AlwaysShow: row.ParentID == 0,
		Children:   make([]*entities.MenuRouterItem, 0),
		Component:  "Layout",
		Hidden:     false,
		Name:       xutils.FirstUpper(strings.TrimLeft(row.Path, "/")),
		Path:       row.Path,
		Meta: &entities.MenuRouterMeta{
			Icon:    row.Icon,
			Link:    nil,
			Title:   row.MenuName,
			NoCache: false,
		},
	}
	item.Hidden = row.Visible == "1"
	if row.Component != "" {
		item.Component = row.Component
	}
	if row.ParentID != 0 && item.Component == "Layout" && row.MenuType == entities.MenuTypeM {
		item.Component = "ParentView"
	}
	if row.IsFrame == 0 {
		item.Meta.Link = xptr.String(row.Path)
	}
	item.Meta.NoCache = cast.ToBool(row.IsCache)
	if row.ParentID == 0 && row.IsFrame == 1 {
		item.Path = "/" + strings.TrimLeft(row.Path, "/")
	}
	if row.MenuType == entities.MenuTypeM {
		item.Redirect = "noRedirect"
	}

	return item
}

func (s *menu) List(ctx *gin.Context) ([]*model.SysMenu, error) {
	var req entities.MenuPageListReq
	if err := ctx.ShouldBind(&req); err != nil {
		return nil, err
	}
	conds := make([]gen.Condition, 0)
	if req.MenuName != "" {
		conds = append(conds, dao.SysMenu.MenuName.Like(`%`+req.MenuName+`%`))
	}
	if req.Status != "" {
		conds = append(conds, dao.SysMenu.Status.Eq(req.Status))
	}
	if cond := s.formatUserConds(ctx); cond != nil {
		conds = append(conds, cond)
	}

	return dao.SysMenu.Where(conds...).Order(dao.SysMenu.ParentID, dao.SysMenu.OrderNum).Find()
}

func (s *menu) Save(ctx *gin.Context) (err error) {
	var req entities.MenuSaveReq
	if err = ctx.ShouldBindJSON(&req); err != nil {
		return err
	}
	if ctx.Request.Method == http.MethodPut && req.MenuID == 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	if err = req.Check(); err != nil {
		return err
	}

	conds := []gen.Condition{
		dao.SysMenu.ParentID.Eq(req.ParentID),
		dao.SysMenu.MenuName.Eq(req.MenuName),
	}
	if req.MenuID > 0 {
		conds = append(conds, dao.SysMenu.MenuID.Neq(req.MenuID))
	}
	if count, _ := dao.SysMenu.Where(conds...).Count(); count > 0 {
		return errors.Errorf("保存菜单'%s'失败，菜单名称已存在", req.MenuName)
	}
	row := &model.SysMenu{}
	if req.MenuID > 0 {
		row, err = dao.SysMenu.Where(dao.SysMenu.MenuID.Eq(req.MenuID)).Take()
		if err != nil {
			return err
		}
	}

	if req.Status == "" {
		req.Status = constants.OkStatus
	}
	if req.Visible == "" {
		req.Visible = "0"
	}
	if req.IsFrame == "" {
		req.IsFrame = "1"
	}
	if req.IsCache == "" {
		req.IsCache = "0"
	}
	if req.Icon == "" {
		req.Icon = "#"
	}

	userInfo := tools.GetUserInfo(ctx)
	row.MenuName = req.MenuName
	row.ParentID = req.ParentID
	row.OrderNum = *req.OrderNum
	row.Path = req.Path
	row.Component = req.Component
	row.Query = req.Query
	row.IsFrame = cast.ToInt64(req.IsFrame)
	row.IsCache = cast.ToInt64(req.IsCache)
	row.MenuType = req.MenuType
	row.Visible = req.Visible
	row.Status = req.Status
	row.Perms = req.Perms
	row.Icon = req.Icon
	row.Remark = req.Remark
	row.UpdateBy = userInfo.User.UserName
	row.UpdateTime = xtime.Time(time.Now())
	if req.MenuID == 0 {
		row.CreateBy = userInfo.User.UserName
		row.CreateTime = xtime.Time(time.Now())
		return dao.SysMenu.Create(row)
	}
	data := xgorm.ModelToMap(row)
	_, err = dao.SysMenu.Where(dao.SysMenu.MenuID.Eq(req.MenuID)).Updates(data)
	if err == nil {
		s.updateUserCache(req.MenuID)
	}
	return err
}

func (s *menu) updateUserCache(id int64) {
	xutils.RecoverGo(func() {
		rows, _ := dao.SysUserRole.Where(dao.SysUserRole.Columns(dao.SysUserRole.RoleID).In(dao.SysRoleMenu.Select(dao.SysRoleMenu.RoleID).Where(dao.SysRoleMenu.MenuID.Eq(id)))).Find()
		if len(rows) > 0 {
			for _, v := range rows {
				User.LoginUserClean(v.UserID)
			}
		}
	}, commonTools.ErrorHandler(context.Background(), "通过菜单更新登录权限,menuId:%v", id))
}

func (s *menu) Delete(ctx *gin.Context) error {
	id := cast.ToInt64(ctx.Param("id"))
	if id == 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	if count, _ := dao.SysMenu.Where(dao.SysMenu.ParentID.Eq(id)).Count(); count > 0 {
		return httpResp.ErrWarning("存在子菜单,不允许删除")
	}

	if count, _ := dao.SysRoleMenu.Where(dao.SysRoleMenu.MenuID.Eq(id)).Count(); count > 0 {
		return httpResp.ErrWarning("菜单已分配,不允许删除")
	}
	_, err := dao.SysMenu.Where(dao.SysMenu.MenuID.Eq(id)).Delete()
	return err
}

func (s *menu) Tree(ctx *gin.Context, status string) ([]*entities.MenuTreeItem, error) {
	conds := make([]gen.Condition, 0)
	if status != "" {
		conds = append(conds, dao.SysMenu.Status.Eq(status))
	}
	if cond := s.formatUserConds(ctx); cond != nil {
		conds = append(conds, cond)
	}
	rows, err := dao.SysMenu.Select(
		dao.SysMenu.MenuID,
		dao.SysMenu.MenuName,
		dao.SysMenu.ParentID,
	).Where(conds...).Order(dao.SysMenu.ParentID, dao.SysMenu.OrderNum).Find()

	if err != nil {
		return nil, err
	}

	list := make([]*entities.MenuTreeItem, 0)

	if len(rows) == 0 {
		return list, nil
	}

	dataMap := make(map[int64]*entities.MenuTreeItem)

	for _, v := range rows {
		row := v
		item := &entities.MenuTreeItem{
			Id:       row.MenuID,
			Label:    row.MenuName,
			Children: make([]*entities.MenuTreeItem, 0),
		}

		dataMap[row.MenuID] = item
		if row.ParentID == 0 {
			list = append(list, item)
		}
	}
	for _, v := range rows {
		if v.ParentID == 0 {
			continue
		}
		dataMap[v.ParentID].Children = append(dataMap[v.ParentID].Children, dataMap[v.MenuID])
	}

	return list, nil
}

func (s *menu) RoleTree(ctx *gin.Context) (map[string]any, error) {
	id := cast.ToInt64(ctx.Param("id"))
	if id == 0 {
		return nil, httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	menuIds := make([]int64, 0)
	info, err := dao.SysRole.Where(dao.SysRole.RoleID.Eq(id)).Take()
	if err != nil {
		return nil, err
	}
	conds := []gen.Condition{
		dao.SysRoleMenu.RoleID.Eq(id),
	}
	if info.MenuCheckStrictly == 1 {
		conds = append(conds, dao.SysMenu.Columns(dao.SysMenu.MenuID).NotIn(
			dao.SysMenu.Select(dao.SysMenu.ParentID).
				Join(dao.SysRoleMenu, dao.SysMenu.MenuID.EqCol(dao.SysRoleMenu.MenuID)).Where(dao.SysRoleMenu.RoleID.Eq(id)),
		))
	}
	rows, err := dao.SysMenu.
		Select(dao.SysMenu.MenuID).
		LeftJoin(dao.SysRoleMenu, dao.SysRoleMenu.MenuID.EqCol(dao.SysMenu.MenuID)).
		Where(conds...).Find()
	if err != nil {
		return nil, err
	}
	for _, v := range rows {
		menuIds = append(menuIds, v.MenuID)
	}
	treeList, err := s.Tree(ctx, "")
	if err != nil {
		return nil, err
	}
	return map[string]any{
		"checkedKeys": menuIds,
		"menus":       treeList,
	}, nil
}

func (s *menu) formatUserConds(ctx *gin.Context) gen.Condition {
	userInfo := tools.GetUserInfo(ctx)
	if !userInfo.IsSuperAdmin() {
		roleId := make([]int64, 0)
		for _, v := range userInfo.User.Roles {
			roleId = append(roleId, v.RoleID)
		}

		return dao.SysMenu.Columns(dao.SysMenu.MenuID).In(dao.SysRoleMenu.Select(dao.SysRoleMenu.MenuID).Where(dao.SysRoleMenu.RoleID.In(roleId...)))
	}
	return nil
}
